/*
 * ClassifiedDataSet.java
 */
package OLDSTUFF.ClassifiedDataSetPackage;

import OLDSTUFF.HierarchyPackage.HClass;
import org.osdtsystem.utils.FastintArrayReturn;
import org.osdtsystem.utils.Storage;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.osdtsystem.utils.TroveIntHashSet;

/**
 * Implementation of ClassifiedDataSet. This class saves its data in the 'storage' provided for it.
 * @author Martin Haulrich
 */
public class ClassifiedDataSetStorage implements ClassifiedDataSet {

    /**
     * Provides data structures for this class.
     */
    final Storage storage;

    /**
     * Map from a to all classes at that depth.
     */
    Map<Integer, Set<HClass>> depthToClass;

    /**
     * Map from ID of classification to depth of classification.
     */
    Map<HClass, Integer> classToDepth;

    /**
     * Map from ID of classification to a set of instances of that classification.
     */
    Map<HClass, Set<Integer>> classToInstances;

    /**
     * The number of data points in the class that is currently biggest.
     */
    int currentMaxSize;

    /**
     * Should the classified data points be saved or not.
     */
    final boolean saveData;

    /**
     * Minimum number of data points there should be in a class to add it to the set.
     */
    final int minCount;

    /**
     * Only keep calassification that holds at least a 'cutFraction' part of the biggest class in the set
     */
    final int cutFraction;

    /**
     * Create new set.
     * Data points are saved.
     * @param storage Provides the data-collections classes used by this set.
     * @param minCount Minimum number of data points there should be in a class to add it to the set.
     */
    public ClassifiedDataSetStorage(Storage storage, int minCount, int cutFraction) {
        this(storage, true, minCount, cutFraction);
    }

    /**
     * Create new set.
     * @param storage Provides the data-collections classes used by this set.
     * @param saveData Should the classified data points be saved or not.
     * @param minCount Minimum number of data points there should be in a class to add it to the set.
     */
    public ClassifiedDataSetStorage(Storage storage, boolean saveData, int minCount, int cutFraction) {
        this.storage = storage;
        this.saveData = saveData;
        this.minCount = minCount;
        this.cutFraction = cutFraction;

        depthToClass = storage.<Integer, Set<HClass>>newMap((Object[]) null);

        classToDepth = storage.<HClass, Integer>newMap((Object[]) null);

        classToInstances = storage.<HClass, Set<Integer>>newMap((Object[]) null);


        currentMaxSize = 0;
    }

    @Override
    public void addClassificationWithData(HClass classification, int depth, Collection<Integer> instances) {

        if (classToDepth.containsKey(classification)) {
            throw new UnsupportedOperationException("This is no longer supported - add all at once.");
        }

        int size = instances.size();
        if ((size > (currentMaxSize / cutFraction)) && (size >= minCount)) {

            currentMaxSize = Math.max(currentMaxSize, instances.size());

            classToDepth.put(classification, depth);

            if (!depthToClass.containsKey(depth)) {
                depthToClass.put(depth, new HashSet<HClass>());
            }
            depthToClass.get(depth).add(classification);

            if (saveData) {

                int[] inst;
                if (instances instanceof FastintArrayReturn) {
                    inst = ((FastintArrayReturn) instances).getArray();
                } else {
                    // Copy instances to int[]
                    inst = new int[instances.size()];
                    int i = 0;
                    for (Integer l : instances) {
                        inst[i++] = l;
                    }
                }
                /**
                 * @TODO Use storage here.
                 */
//                classToInstances.put(classification, new TroveIntList(inst));
                classToInstances.put(classification, new TroveIntHashSet(inst));
            }
        }
    }

    @Override
    public int size() {
        int size = 0;
        Set<HClass> keys = classToInstances.keySet();
        for (Iterator<HClass> it = keys.iterator(); it.hasNext();) {
            HClass c = it.next();
            size = size + classToInstances.get(c).size();
        }
        return size;
    }

    @Override
    public int classCount() {
        return classToInstances.size();
    }

    @Override
    public boolean containsClass(HClass classification) {
        return classToDepth.containsKey(classification);
    }

    @Override
    public Set<Integer> getInstances(HClass classification) {
        Set<Integer> ret = classToInstances.get(classification);
        if (ret == null) {
            ret = Collections.<Integer>emptySet();

        }
        return ret;
    }

    @Override
    public Collection<HClass> classifications() {

        return classToDepth.keySet();
    }

    @Override
    public Collection<HClass> classificationsAtDepth(int depth) {

        if (depthToClass.containsKey(depth)) {

            return depthToClass.get(depth);
        }
        return Collections.<HClass>emptyList();

    }

    @Override
    public Collection<HClass> classificationsAtDepthSorted(int depth) {
        SortedSet<HClass> sortedClassifications = new TreeSet<HClass>(new classificationComparator());
        if (depthToClass.containsKey(depth)) {

            sortedClassifications.addAll(depthToClass.get(depth));
        }
        return sortedClassifications;
    }

    @Override
    public int getDepth(HClass classification) {

        return classToDepth.get(classification).intValue();
    }

    @Override
    public int getCount(HClass classification) {
        if (!containsClass(classification)) {
            return 0;
        } else {
            return classToInstances.get(classification).size();
        }
    }

    class classificationComparator implements Comparator<HClass> {

        @Override
        public final int compare(HClass a, HClass b) {
            Integer ca = classToInstances.get(a).size();
            Integer cb = classToInstances.get(b).size();

            if (ca.equals(cb)) {
                return 1;
            } else {
                return cb.compareTo(ca);
            }
        }
    }
}
